import _ from 'lodash';
import DB from '../db';
import mysql from 'mysql';
import SQL from './mysql.sql';
import {compact} from '../..';
import SB from '../../string-buffer';
const debug = require('debug')('db:mysql');

// 字段类型格式（如：enum('N','Y')）
const FIELD_TYPE = /(.+)\((.+)\)/;

/* eslint no-console: 0 */
export default class MySQL extends DB {
    /**
     * 构造函数
     * @param config 除通用参数外，还包括：
     * multi - 是否启用多语句
     * charset - 字符集
     */
    constructor(config) {
        super(config);
        this.sql = new SQL();
        const cfg = config || {};
        const opt = {
            host: cfg.host || '127.0.0.1',
            port: cfg.port || 3306,
            user: cfg.username || 'root',
            password: cfg.password,
            database: cfg.database,
            multipleStatements: cfg.multi,
            charset: cfg.charset
        };
        if (typeof cfg.pool_size === 'number') {
            // 指定了连接池大小则使用连接池
            if (cfg.pool_size > 0) {
                opt.connectionLimit = cfg.pool_size;
            }
            const pool = mysql.createPool(opt);
            pool.on('enqueue', () => {
                console.log('!mysql enqueue');
            });
            this.client = pool;
            this.isPool = true;
        } else {
            // 否则直接创建连接对象
            this.client = mysql.createConnection(opt);
        }
        this.escapeId = mysql.escapeId;
    }

    /////////////////////////////////////////////////////////////////
    // 连接

    open() {
        if (this.isPool) {
            return new Promise((done, fail) => {
                this.client.getConnection((err, conn) => {
                    if (err) {
                        fail(err);
                    } else {
                        this.c = conn;
                        done(this);
                    }
                });
            });
        } else {
            return new Promise((done, fail) => {
                this.client.connect(err => {
                    if (err) {
                        fail(err);
                    } else {
                        this.c = this.client;
                        done(this);
                    }
                });
            });
        }
    }

    close() {
        const {c} = this;
        if (!c) {
            return Promise.resolve();
        }
        if (typeof c.release === 'function') {
            return new Promise((done) => {
                c.release();
                this.c = null;
                done();
            });
        } else {
            return new Promise((done) => {
                c.end(() => {
                    this.c = null;
                    done();
                });
            });
        }
    }

    begin() {
        return new Promise((done, fail) => {
            this.c.beginTransaction(err => {
                if (err) {
                    fail(err);
                } else {
                    done(this);
                }
            });
        });
    }

    commit() {
        return new Promise((done, fail) => {
            this.c.commit(err => {
                if (err) {
                    fail(err);
                } else {
                    done();
                }
            });
        });
    }

    abort() {
        return new Promise((done) => {
            this.c.rollback(() => {
                done();
            });
        });
    }

    /////////////////////////////////////////////////////////////////
    // 执行

    run(sql, setResult, params) {
        if (!this.c) {
            throw new Error('NOT connected');
        }
        return new Promise((done, fail) => {
            this.c.query(sql, params, (err, rows, fields) => {
                if (err) {
                    fail(err);
                } else {
                    if (!setResult) {
                        setResult = rows => rows;
                    }
                    done(setResult(rows, fields));
                }
            });
        });
    }

    /////////////////////////////////////////////////////////////////
    // 库

    dbs() {
        return this.run('SHOW DATABASES', (rows, fields) => (
            _.map(rows, fields[0].name)
        ));
    }

    tables(db) {
        const sql = new SB('SHOW TABLES');
        if (db) {
            sql.add(' FROM ').add(this.sql.id(db));
        }
        return this.run(sql.str(), (rows, fields) =>
            _.map(rows, fields[0].name)
        );
    }

    createDb(opt) {
        const sql = this.sql.createDb(opt);
        return this.run(sql.str());
    }

    removeDb(db, ...opt) {
        const sql = this.sql.removeDb(db, opt);
        return this.run(sql.str());
    }

    /////////////////////////////////////////////////////////////////
    // 表

    createTable(opt) {
        const sql = this.sql.createTable(opt);
        return this.run(sql.str());
    }

    updateTable(opt) {
        const sql = this.sql.updateTable(opt);
        return this.run(sql.str());
    }

    removeTable(opt) {
        const sql = this.sql.removeTable(opt);
        return this.run(sql.str());
    }

    fields(opt) {
        const sql = new SB('DESC ');
        sql.add(this.sql.id(opt.db, opt.table));
        return this.run(sql.str(), rows =>
            rows.map(row => {
                let type = row.Type, length, values;
                const m = type.match(FIELD_TYPE);
                if (m) {
                    type = m[1];
                    length = parseInt(m[2]);
                    if (!length) { // 不是数字（可能是NaN）
                        length = undefined;
                        values = m[2].split(',').map(value =>
                            value.substring(1, value.length - 1)
                        );
                    }
                }
                return compact({
                    name: row.Field,
                    type,
                    length,
                    values,
                    default: row.Default,
                    not_null: row.Null === 'NO',
                    unique: row.Key === 'UNI',
                    key: row.Key === 'PRI'
                });
            })
        );
    }

    /////////////////////////////////////////////////////////////////
    // 查询

    query(opt, params) {
        const sql = this.sql.query(opt);
        if (opt.sqlOnly !== undefined) {
            return Promise.resolve(sql.str());
        }
        let cb;
        if (opt.withFields !== undefined) {
            cb = (rows, fields) => ({
                rows,
                fields: fields.map(field => _.pick(field, [
                    'name', 'type', 'length', 'decimals', 'default'
                ]))
            });
        }
        const s = sql.str();
        debug('query', opt, s);
        return this.run(s, cb, sql.params || params);
    }

    count(opt, params) {
        const sql = this.sql.count(opt);
        return this.run(sql.str(), (rows, fileds) => {
            let count = 0;
            if (rows.length === 1) {
                count = parseInt(rows[0][fileds[0].name]);
            }
            return count;
        }, sql.params || params);
    }
}
